using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.IO;
using System.Xml;

namespace Woz
{
    /// <summary>
    /// stroke class used by the pen tool
    /// </summary>
    internal class Stroke : Woz.Entity
    {
        /// <summary>
        /// creates a new stroke from an xml stream
        /// </summary>
        /// <param name="pStream">the xml stream to load the stroke data from</param>
        /// <param name="pBoundingBox">the boundin box of the entity</param>
        /// <param name="pParams">the path of the xml stream</param>
        public Stroke(XmlReader pStream, Rectangle pBoundingBox, string pPath)
            : base(pBoundingBox.Location, pBoundingBox.Size)
        {
            // variables
            Color penColor;
            float penThickness;
            List<Point> points = new List<Point>();

            // load entity from xml
            pStream.ReadStartElement("Stroke", "urn:Woz");
            
            pStream.ReadStartElement("Pen");
            penColor = Color.FromArgb(int.Parse(pStream.ReadElementString("Color"), System.Globalization.NumberStyles.HexNumber));
            penThickness = float.Parse(pStream.ReadElementString("Thickness"));
            pStream.ReadEndElement();
            
            while (pStream.IsStartElement("Point"))
            {
                int x = int.Parse(pStream.GetAttribute("X"));
                int y = int.Parse(pStream.GetAttribute("Y"));
                points.Add(new Point(x, y));

                pStream.ReadElementString("Point");
            }
            
            pStream.ReadEndElement();

            // initialize the stroke
            this.Initialize(points.ToArray(), Woz.ResourceManager.Instance.GetPen(penColor, penThickness));
        }

        /// <summary>
        /// default constructor of a stroke 
        /// </summary>
        /// <param name="pPoints">the list of points that make up the stroke</param>
        /// <param name="pPen">the pen to draw stroke with</param>
        public Stroke(Point[] pPoints, Pen pPen)
            : base(Point.Empty, Size.Empty)
        {
            this.Initialize(pPoints, pPen);
        }

        /// <summary>
        /// private copy constructor of a stroke used by clone
        /// </summary>
        /// <param name="pStroke">the stroke to copy</param>
        private Stroke(Woz.Stroke pStroke)
            : base(Point.Empty, Size.Empty)
        {
            this.Locked = pStroke.Locked;
            this.Initialize(pStroke.mInternalStroke.GetPoints(), pStroke.Pen);
        }

        /// <summary>
        /// initializes the newly created stroke object
        /// </summary>
        /// <param name="pPoints">the points of the stroke</param>
        /// <param name="pPen">the pen to draw the stroke with</param>
        private void Initialize(Point[] pPoints, Pen pPen)
        {
            // initialize the internal stroke
            this.mInternalStroke = Woz.ResourceManager.Instance.Ink.CreateStroke(pPoints);
            this.mInternalStroke.DrawingAttributes.PenTip = Microsoft.Ink.PenTip.Ball;
            this.mInternalStroke.DrawingAttributes.Color = pPen.Color;
            this.mInternalStroke.DrawingAttributes.Width = pPen.Width;

            // get the bounding box of the internal stroke
            Rectangle boundingBox = this.mInternalStroke.GetBoundingBox();

            // set the bounding box of the internal stroke to the bounding box of this entity
            this.mBoundingBox.X = boundingBox.X;
            this.mBoundingBox.Y = boundingBox.Y;
            this.mBoundingBox.Width = boundingBox.Width;
            this.mBoundingBox.Height = boundingBox.Height;

            // initialize the pen
            this.mPen = pPen;
        }

        /// <summary>
        /// get the pen to draw the stroke with
        /// </summary>
        public Pen Pen
        {
            get
            {
                return this.mPen;
            }
        }

        /// <summary>
        /// checks to see if a point with x and y intersects this stroke
        /// </summary>
        /// <param name="pX">the x coordinate of the point</param>
        /// <param name="pY">the y coordnitae of the point</param>
        /// <returns>true if the point intersects with the stroke false otherwise</returns>
        public override bool HitTest(int pX, int pY)
        {
            return this.mInternalStroke.HitTest(new Point(pX, pY), this.mPen.Width);
        }

        /// <summary>
        /// draws the stroke on a graphics object
        /// </summary>
        /// <param name="pGraphics">the graphics object to draw on</param>
        public override void Draw(Graphics pGraphics)
        {
            // get the points out of the internal stroke
            Point[] points = this.mInternalStroke.GetPoints();
            
            // loop through each of these points
            for (int i = 1; i < points.Length; i++)
            {
                // and draw a line between two points
                pGraphics.DrawLine(this.mPen, points[i - 1], points[i]);
            }

            // REMOVE: draw the bounding box
            //pGraphics.DrawRectangle(Woz.ResourceManager.Instance.GetPen(Color.Red, 0.01f), this.mBoundingBox);
        }

        /// <summary>
        /// what to do when the stroke is moved
        /// </summary>
        /// <param name="pDeltaX">x position stroke is moved to</param>
        /// <param name="pDeltaY">y position stroke is moved to</param>
        protected override void OnMove(int pDeltaX, int pDeltaY)
        {
            this.mInternalStroke.Move((float)pDeltaX, (float)pDeltaY);
            base.mBoundingBox.X += pDeltaX;
            base.mBoundingBox.Y += pDeltaY;
        }

        /// <summary>
        /// what to do when the stroke is scaled
        /// </summary>
        /// <param name="pDeltaWidth">the relative amount of width</param>
        /// <param name="pDeltaHeight">the relative amount of height</param>
        protected override void OnScale(int pDeltaWidth, int pDeltaHeight)
        {
            // get the scale rectangle
            Rectangle scaleRect = Rectangle.Inflate(this.mBoundingBox, pDeltaWidth, pDeltaHeight);

            // scale the stroke
            this.mInternalStroke.ScaleToRectangle(scaleRect);

            // update the bounding box
            this.mBoundingBox.Width += pDeltaWidth;
            this.mBoundingBox.Height += pDeltaHeight;
        }

        /// <summary>
        /// releases all resources used by the stroke
        /// </summary>
        public override void Dispose()
        {
            // release the internal stroke
            if (this.mInternalStroke != null)
            {
                Woz.ResourceManager.Instance.Ink.DeleteStroke(this.mInternalStroke);
                this.mInternalStroke = null;
            }

            // release the pen
            if (this.mPen != null)
            {
                this.mPen = null;
            }
        }

        /// <summary>
        /// creates an exact copy of this stroke
        /// </summary>
        /// <returns>a copy of the stroke as an object</returns>
        public override object Clone()
        {
            return new Woz.Stroke(this);
        }

        /// <summary>
        /// checks whether or not this entity supports an attribute
        /// </summary>
        /// <param name="pAttribute">the name of the attribute</param>
        /// <returns>true if attribute is supported</returns>
        public override bool SupportsAttribute(string pAttribute)
        {
            // check for color
            if (pAttribute == "color")
            {
                return true;
            }

            // check for thickness
            else if (pAttribute == "thickness")
            {
                return true;
            }

            // if all else fails just return false
            return false;
        }

        /// <summary>
        /// gets a reference to the attribute with the given attribute name
        /// </summary>
        /// <param name="pAttribute">the name of the attribute</param>
        /// <returns>a reference to the attribute</returns>
        public override object GetAttribute(string pAttribute)
        {
            // check for color
            if (pAttribute == "color")
            {
                return (object)this.mPen.Color;
            }

            // check for thickness
            else if (pAttribute == "thickness")
            {
                return (object)this.mPen.Width;
            }
            
            // if all else fails return null
            return null;
        }

        /// <summary>
        /// saves a reference to the attribute with the given attribute name
        /// </summary>
        /// <param name="pAttribute">the name of the attribute</param>
        /// <param name="pObject">a reference to the attribute</param>
        public override void SetAttribute(string pAttribute, object pObject)
        {
            // save color
            if (pAttribute == "color")
            {
                this.mPen = Woz.ResourceManager.Instance.GetPen((Color)pObject, this.mPen.Width);
            }

            // save thickness
            else if (pAttribute == "thickness")
            {
                this.mPen = Woz.ResourceManager.Instance.GetPen(this.mPen.Color, (float)pObject);
            }
        }

        /// <summary>
        /// returns a string representing the stroke
        /// </summary>
        /// <returns>a string representing the stroke</returns>
        public override string ToString()
        {
            // create the action string
            int color = (this.mPen.Color.ToArgb() & 0xFFFFFF);
            float width = this.mPen.Width;
            float normal = Woz.StaticVariables.Instance.InitialPenThickness;
            float fine = normal / 1.8f;
            float bold = normal * 1.8f;

            // start off with the action type
            string actionString = "Stroke";

            // add the color
            if (color == 0x000000)
            {
                actionString = String.Concat("Black ", actionString);
            }
            else if (color == 0x305FEC)
            {
                actionString = String.Concat("Blue ", actionString);
            }
            else if (color == 0xFF00FF)
            {
                actionString = String.Concat("Magenta ", actionString);
            }
            else if (color == 0xFF0000)
            {
                actionString = String.Concat("Red ", actionString);
            }
            else if (color == 0xFF9900)
            {
                actionString = String.Concat("Orange ", actionString);
            }
            else if (color == 0xF9F900)
            {
                actionString = String.Concat("Yellow ", actionString);
            }
            else if (color == 0x00C200)
            {
                actionString = String.Concat("Green ", actionString);
            }
            else
            {
                actionString = String.Concat("Custom Color ", actionString);
            }

            // add the thickness
            if (width == fine)
            {
                actionString = String.Concat("Fine ", actionString);
            }
            else if (width == bold)
            {
                actionString = String.Concat("Bold ", actionString);
            }
            else if (width == normal)
            {
                actionString = String.Concat("Normal ", actionString);
            }
            else
            {
                actionString = String.Format("{0} {1}", this.mPen.Width, actionString);
            }

            // return the action string
            return actionString;
        }

        /// <summary>
        /// handles saving of this entity
        /// </summary>
        /// <param name="pStream">the stream to save to</param>
        /// <param name="pPath">the path of the temporary files</param>
        protected override void OnSave(XmlWriter pStream, string pPath)
        {
            pStream.WriteStartElement("Stroke", "urn:Woz");
            pStream.WriteStartElement("Pen");
            pStream.WriteElementString("Color", this.mPen.Color.ToArgb().ToString("X"));
            pStream.WriteElementString("Thickness", this.mPen.Width.ToString());
            pStream.WriteEndElement();

            Point[] points = this.mInternalStroke.GetPoints();
            foreach (Point p in points)
            {
                pStream.WriteStartElement("Point");
                pStream.WriteAttributeString("X", p.X.ToString());
                pStream.WriteAttributeString("Y", p.Y.ToString());
                pStream.WriteEndElement();
            }

            pStream.WriteEndElement();
        }

        /// <summary>
        /// the internal stroke that we are managing
        /// </summary>
        private Microsoft.Ink.Stroke mInternalStroke;

        /// <summary>
        /// the pen that we draw with
        /// </summary>
        private Pen mPen;
    }
}
